﻿// Copyright (C) 2009 Monadsphere <monadshpere@gmail.com>
//
// This file is part of Monadsphere.
//
// Monadsphere is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Monadsphere is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Monadsphere. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading;

/// <summary>
/// This namespace contains core classes that wraps javascript.
/// </summary>
/// 

namespace monadsphere.js {

    /// <summary>
    /// JSContext serves for:
    /// 1. creating javascript variables through simple expressions;
    /// 2. creating javascript variables by calling constructors;
    /// 3. getting / setting properties of a variable;
    /// 4. calling procedures of a variable;
    /// 5. calling functions of a variable and return results;
    /// </summary>
    /// 
    public class JSContext {

        /// <summary>
        /// Static thread local singleton for serving the whole lifecycle
        /// of thread
        /// </summary>
        [ThreadStatic]
        protected static JSContext theContext;

        /// <summary>
        /// for generating javascript code
        /// </summary>
        protected TextWriter writer;

        /// <summary>
        /// variable sequence
        /// </summary>
        protected int varSeq;

        /// <summary>
        /// Creates instance with a TextWriter.
        /// </summary>
        /// <param name="writer">writer</param>
        public JSContext(TextWriter writer) {
            this.writer = writer;
        }

        /// <summary>
        /// Returns the context for current thread. Setter is called internally.
        /// </summary>
        public static JSContext context {
            get {
                return theContext;
            }

            set {
                theContext = value;
            }
        }

        /// <summary>
        /// Creates a javascript variable and return variable name.
        /// 
        /// For example,
        /// string name = JSContext.context.createVariable("15");
        /// 
        /// </summary>
        /// <param name="definition"></param>
        /// <returns></returns>
        public string createVariable(string definition) {
            string name = "_" + ++varSeq;
            writer.WriteLine("var " + name + '=' + definition);
            return name;
        }

        public string createVariable(string name, string definition) {
            writer.WriteLine("var " + name + '=' + definition);
            return name;
        }

        /// <summary>
        /// Returns a property of a variable.
        /// </summary>
        /// <param name="variable">any variable</param>
        /// <param name="propName">property name</param>
        /// <returns></returns>
        public string getProperty(object variable, string propName) {
            return createVariable(variable.ToString() + "." + propName);
        }

        public string getProperty(object variable, JSVar propName) {
            return createVariable(variable.ToString() + "." + propName);
        }

        /// <summary>
        /// Sets a property of a variable.
        /// </summary>
        /// <param name="variable">any variable</param>
        /// <param name="propName">property name</param>
        /// <param name="propValue">property value</param>
        public void setProperty(object variable, string propName, object propValue) {
            writer.WriteLine(variable + "." + propName + "=" + propValue);
        }

        public void setProperty(object variable, JSVar propName, object propValue) {
            writer.WriteLine(variable + "." + propName + "=" + propValue);
        }

        /// <summary>
        /// Gets an indexed value.
        /// </summary>
        /// <param name="variable"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public string getIndex(object variable, object index) {
            return createVariable(variable + "[" + index.ToString() + "]");
        }

        /// <summary>
        /// Sets an indexed value.
        /// </summary>
        /// <param name="variable"></param>
        /// <param name="index"></param>
        /// <param name="value"></param>
        public void setIndex(object variable, object index, object value) {
            writer.WriteLine(variable + "[" + index.ToString() + "]=" + value);
        }

        /// <summary>
        /// Returns javascript code that calls a method with any number of parameters.
        /// </summary>
        /// <param name="name">script name</param>
        /// <param name="args">arguments</param>
        /// <returns></returns>
        public string methodCallScript(string name, params object[] args) {
            StringBuilder sb = new StringBuilder();
            sb.Append(name).Append("(");
            if (args != null && args.Length > 0) {
                for (int i = 0; i < args.Length; i++) {
                    object arg = args[i];
                    if (arg == null) {
                        sb.Append("null");
                    } else if (args[i] is bool) {
                        sb.Append((bool) args[i] ? "true" : "false");
                    } else {
                        sb.Append(args[i]);
                    }

                    if (i < args.Length - 1) {
                        sb.Append(",");
                    }
                }
            }
            sb.Append(")");

            return sb.ToString();
        }

        /// <summary>
        /// Calls a constructor, such as "new func(arg1, arg2)"
        /// </summary>
        /// <param name="name">constructor name</param>
        /// <param name="args">arguments</param>
        /// <returns></returns>
        public string callConstructor(string name, params object[] args) {
            return createVariable(methodCallScript("new " + name, args));
        }

        /// <summary>
        /// Calls a procedure of a variable.
        /// </summary>
        /// <param name="variable">any variable</param>
        /// <param name="name">procedure name</param>
        /// <param name="args">arguments</param>
        public void callProcedure(object variable, string name, params object[] args) {
            writer.WriteLine(methodCallScript(variable + "." + name, args));
        }

        /// <summary>
        /// Calls a function of a variable.
        /// </summary>
        /// <param name="variable">any variable</param>
        /// <param name="name">function name</param>
        /// <param name="args">parameters</param>
        /// <returns></returns>
        public string callFunction(object variable, string name, params object[] args) {
            return createVariable(methodCallScript(variable + "." + name, args));
        }

        /// <summary>
        /// Writes content to client.
        /// </summary>
        /// <param name="content"></param>
        public void write(string content) {
            writer.Write(content);
        }

        /// <summary>
        /// Writes content to client into a line.
        /// </summary>
        /// <param name="content"></param>
        public void writeln(string content) {
            writer.WriteLine(content);
        }

        public void writeln(JSString content) {
            writer.WriteLine(content);
        }
    }

    /// <summary>
    /// Base interface for any interfaces and classes used to represent variables
    /// on client side.
    /// </summary>
    public interface JSAny {
    }

    /// <summary>
    /// Base class for any instance that represents a variable on client side.
    /// </summary>
    public class JSVar: JSAny {

        /// <summary>
        /// Name of the variable on client side.
        /// </summary>
        protected string name;
        protected bool create;

        protected JSVar() {
        }

        /// <summary>
        /// Copy constructor that copies name. Thus the new handle will share the 
        /// same name as the source stub. This avoids generating another javascript variable.
        /// </summary>
        /// <param name="src"></param>
        public JSVar(JSVar src)
            : this() {
            name = src.name;
        }

        /// <summary>
        /// Creates a javascript variable with generated name.
        /// 
        /// For example:
        ///   JSVar var = new JSVar("document.createElement('div')");
        ///   
        /// will emit the following javascript code:
        ///   var _1 = document.createElement('div')
        /// 
        /// </summary>
        /// <param name="value"></param>
        public JSVar(string definition)
            : this(definition, false) {
        }

        /// <summary>
        /// Creates an inline javascript variable.
        /// </summary>
        /// <param name="definition"></param>
        /// <param name="inline"></param>
        public JSVar(string definition, bool inline)
            : this() {
            if (inline) {
                name = definition;
            } else {
                name = JSContext.context.createVariable(definition);
                create = true;
            }
        }

        /// <summary>
        /// Creates a javascript variable with given name and definition.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="definition"></param>
        public JSVar(string name, string definition)
            : this() {
            this.name = JSContext.context.createVariable(name, definition);
            create = true;
        }

        /// <summary>
        /// Inline string constant, which does not create a var on client side.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSVar(string value) {
            return new JSVar("'" + value + "'", true);
        }

        /// <summary>
        /// Inline int constant, which does not create a var on client side.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSVar(int value) {
            return new JSVar(value.ToString(), true);
        }

        /// <summary>
        /// Inline long constant, which does not create a var on client side.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSVar(long value) {
            return new JSVar(value.ToString(), true);
        }

        /// <summary>
        /// Inline double constant, which does not create a var on client side.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSVar(double value) {
            return new JSVar(value.ToString(), true);
        }

        /// <summary>
        /// Inline boolean constant, which does not create a var on client side.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSVar(bool value) {
            return new JSVar(value ? "true" : "false", true);
        }

        /// <summary>
        /// Converts a NamedValues into a javascript object.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSVar(NamedValues value) {
            JSVar handle = new JSVar("{}");
            if (value != null) {
                NamedValues.Enumerator iter = value.GetEnumerator();
                while (iter.MoveNext()) {
                    handle.setProperty(iter.Current.Key, iter.Current.Value);
                }
            }
            return handle;
        }

        /// <summary>
        /// Converts a List into a javascript array.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSVar(List<NamedValues> value) {
            JSVar handle = new JSVar("new Array()");
            if (value != null) {
                for (int i = 0; i < value.Count; i++) {
                    JSVar v = value[i];
                    handle.setIndex(i, v);
                }
            }
            return handle;
        }

        /// <summary>
        /// Operator ! overloading to detect existance of a variable.
        /// It return an expression.
        /// 
        /// This is useful to mimic the following javascript code:
        /// 
        /// var a = ...
        /// if (!a) {
        ///   ...
        /// }
        /// 
        /// The C# code can be as follows:
        /// 
        /// JSVar a = ...
        /// JSStmt.IF(!a, () =>{
        ///   ...
        /// });
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        public static JSExpr operator !(JSVar src) {
            return new JSExpr("!" + src.ToString());
        }

        /// <summary>
        /// Assigns another variable to this.
        /// </summary>
        /// <param name="src"></param>
        public void set(JSVar src) {
            JSContext.context.writeln(this + "=" + src);
        }

        public bool initialize {
            get {
                return create;
            }
        }

        /// <summary>
        /// Returns a variable with known name.
        /// 
        /// For example:
        ///   JSVar doc = JSVar.getExistingVariable("document");
        ///   
        /// This will not emit any code.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static T getExistingVariable<T>(string name) where T: JSVar {
            JSVar result = new JSVar();
            result.name = name;
            return createVariable<T>(result);
        }

        /// <summary>
        /// Calls a constructor.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static JSVar callConstructor(string name, params object[] args) {
            return new JSVar(JSContext.context.methodCallScript("new " + name, args));
        }

        /// <summary>
        /// Retrieves a property in the form of JSVar.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public T getProperty<T>(string name) where T: JSAny {
            JSVar h = new JSVar();
            h.name = JSContext.context.getProperty(this, name);
            return createVariable<T>(h);
        }

        public T getProperty<T>(JSVar name) where T: JSAny {
            JSVar h = new JSVar();
            h.name = JSContext.context.getProperty(this, name);
            return createVariable<T>(h);
        }

        /// <summary>
        /// Returns a property as an inline variable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        public T getPropertyInline<T>(string name) where T: JSAny {
            JSVar h = new JSVar();
            h.name = this.ToString() + "." + name;
            return createVariable<T>(h);
        }

        /// <summary>
        /// Sets a property. The property value can be any object with
        /// which ToString() is always called to get the actual value.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public void setProperty(string name, object value) {
            JSContext.context.setProperty(this.name, name, value);
        }

        public void setProperty(JSVar name, object value) {
            JSContext.context.setProperty(this.name, name, value);
        }

        public T getIndex<T>(object index) where T: JSVar {
            JSVar h = new JSVar();
            h.name = JSContext.context.getIndex(this, index);
            return createVariable<T>(h);
        }

        public void setIndex(object index, object value) {
            JSContext.context.setIndex(this.name, index, value);
        }

        /// <summary>
        /// Calls a method with an array of parameters.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="args"></param>
        public void callProcedure(string name, params object[] args) {
            JSContext.context.callProcedure(this, name, args);
        }

        /// <summary>
        /// Calls a method that takes arguments and returns stub of the result.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public T callFunction<T>(string name, params object[] args) {
            return createVariable<T>(new JSVar(JSContext.context.methodCallScript(this + "." + name, args)));
        }

        /// <summary>
        /// Calls a function and returns an inline variable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public T callFunctionInline<T>(string name, params object[] args) {
            return createVariable<T>(new JSVar(JSContext.context.methodCallScript(this + "." + name, args), true));
        }

        /// <summary>
        /// Creates a variable as type T by calling T(JSVar).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="handle"></param>
        /// <returns></returns>
        public static T createVariable<T>(JSVar handle) {
            Type type = typeof(T);

            // if T is JSVar, return
            if (type == typeof(JSVar)) {
                return (T) (object) handle;
            }

            ConstructorInfo ci = type.GetConstructor(new Type[] { typeof(JSVar) });
            if (ci == null) {
                throw new Exception(type.Name + " does not have copy constructor for JSVar!");
            }
            return (T) ci.Invoke(new object[] { handle });
        }

        /// <summary>
        /// This simply returns name.
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            return name;
        }
    }

    public class NamedValues: Dictionary<string, JSVar> {
        public NamedValues() {
        }
    }

    /// <summary>
    /// This class corresponds to javascript Boolean type.
    /// </summary>
    public class JSBoolean: JSVar {

        /// <summary>
        /// Constant constructor.
        /// </summary>
        /// <param name="value"></param>
        internal JSBoolean(bool value)
            : base(value ? "true" : "false", true) {
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="src"></param>
        public JSBoolean(JSVar src)
            : base(src) {
        }

        /// <summary>
        /// Converts a native bool into javascript boolean.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSBoolean(bool value) {
            return new JSBoolean(value);
        }

        public JSString toString() {
            return callFunction<JSString>("toString");
        }
    }

    /// <summary>
    /// This class corresponds to javascript Date type.
    /// </summary>
    public class JSDate: JSVar {

        public JSDate(JSVar src)
            : base(src) {
        }

        public JSDate()
            : base(callConstructor("Date")) {
        }

        public JSDate(JSNumber milliseconds)
            : base(callConstructor("Date", milliseconds)) {
        }

        public JSDate(JSString dateString)
            : base(callConstructor("Date", dateString)) {
        }

        public JSDate(JSNumber year, JSNumber month, JSNumber date)
            : base(callConstructor("Date", year, month, date)) {
        }

        public JSDate(JSNumber year, JSNumber month, JSNumber date, JSNumber hours)
            : base(callConstructor("Date", year, month, date, hours)) {
        }

        public JSDate(JSNumber year, JSNumber month, JSNumber date, JSNumber hours, JSNumber minutes)
            : base(callConstructor("Date", year, month, date, hours, minutes)) {
        }

        public JSDate(JSNumber year, JSNumber month, JSNumber date, JSNumber hours, JSNumber minutes, JSNumber seconds)
            : base(callConstructor("Date", year, month, date, hours, minutes, seconds)) {
        }

        public JSDate(JSNumber year, JSNumber month, JSNumber date, JSNumber hours, JSNumber minutes, JSNumber seconds, JSNumber millisecond)
            : base(callConstructor("Date", year, month, date, hours, minutes, seconds, millisecond)) {
        }

        public static JSNumber parse(JSString dateString) {
            return JSVar.getExistingVariable<JSVar>("Date").callFunction<JSNumber>("parse", dateString);
        }

        public static JSNumber UTC(JSNumber year, JSNumber month, JSNumber date) {
            return JSVar.getExistingVariable<JSVar>("Date").callFunction<JSNumber>("UTC", year, month, date);
        }

        public static JSNumber UTC(JSNumber year, JSNumber month, JSNumber date, JSNumber hrs) {
            return JSVar.getExistingVariable<JSVar>("Date").callFunction<JSNumber>("UTC", year, month, date, hrs);
        }

        public static JSNumber UTC(JSNumber year, JSNumber month, JSNumber date, JSNumber hrs, JSNumber min) {
            return JSVar.getExistingVariable<JSVar>("Date").callFunction<JSNumber>("UTC", year, month, date, hrs, min);
        }

        public static JSNumber UTC(JSNumber year, JSNumber month, JSNumber date, JSNumber hrs, JSNumber min, JSNumber sec) {
            return JSVar.getExistingVariable<JSVar>("Date").callFunction<JSNumber>("UTC", year, month, date, hrs, min, sec);
        }

        public static JSNumber UTC(JSNumber year, JSNumber month, JSNumber date, JSNumber hrs, JSNumber min, JSNumber sec, JSNumber ms) {
            return JSVar.getExistingVariable<JSVar>("Date").callFunction<JSNumber>("UTC", year, month, date, hrs, min, sec, ms);
        }

        public JSNumber getDate() {
            return callFunction<JSNumber>("getDate");
        }

        public JSNumber getDay() {
            return callFunction<JSNumber>("getDay");
        }

        public JSNumber getFullYear() {
            return callFunction<JSNumber>("getFullYear");
        }

        public JSNumber getHours() {
            return callFunction<JSNumber>("getHours");
        }

        public JSNumber getMilliseconds() {
            return callFunction<JSNumber>("getMilliseconds");
        }

        public JSNumber getMinutes() {
            return callFunction<JSNumber>("getMinutes");
        }

        public JSNumber getMonth() {
            return callFunction<JSNumber>("getMonth");
        }

        public JSNumber getSeconds() {
            return callFunction<JSNumber>("getSeconds");
        }

        public JSNumber getTime() {
            return callFunction<JSNumber>("getTime");
        }

        public JSNumber getTimezoneOffset() {
            return callFunction<JSNumber>("getTimezoneOffset");
        }

        public JSNumber getUTCDate() {
            return callFunction<JSNumber>("getUTCDate");
        }

        public JSNumber getUTCDay() {
            return callFunction<JSNumber>("getUTCDay");
        }

        public JSNumber getUTCFullYear() {
            return callFunction<JSNumber>("getUTCFullYear");
        }

        public JSNumber getUTCHours() {
            return callFunction<JSNumber>("getUTCHours");
        }

        public JSNumber getUTCMilliseconds() {
            return callFunction<JSNumber>("getUTCMilliseconds");
        }

        public JSNumber getUTCMinutes() {
            return callFunction<JSNumber>("getUTCMinutes");
        }

        public JSNumber getUTCMonth() {
            return callFunction<JSNumber>("getUTCMonth");
        }

        public JSNumber getUTCSeconds() {
            return callFunction<JSNumber>("getUTCSeconds");
        }

        public void setDate(JSNumber dayValue) {
            callProcedure("setDate", dayValue);
        }

        public void setFullYear(JSNumber yearValue) {
            callProcedure("setFullYear", yearValue);
        }

        public void setFullYear(JSNumber yearValue, JSNumber monthValue) {
            callProcedure("setFullYear", yearValue, monthValue);
        }

        public void setFullYear(JSNumber yearValue, JSNumber monthValue, JSNumber dayValue) {
            callProcedure("setFullYear", yearValue, monthValue, dayValue);
        }

        public void setHours(JSNumber hoursValue) {
            callProcedure("setHours", hoursValue);
        }

        public void setHours(JSNumber hoursValue, JSNumber minutesValue) {
            callProcedure("setHours", hoursValue, minutesValue);
        }

        public void setHours(JSNumber hoursValue, JSNumber minutesValue, JSNumber secondsValue) {
            callProcedure("setHours", hoursValue, minutesValue, secondsValue);
        }

        public void setHours(JSNumber hoursValue, JSNumber minutesValue, JSNumber secondsValue, JSNumber msValue) {
            callProcedure("setHours", hoursValue, minutesValue, secondsValue, msValue);
        }

        public void setMilliseconds(JSNumber millisecondsValue) {
            callProcedure("setMilliseconds", millisecondsValue);
        }

        public void setMinutes(JSNumber minutesValue) {
            callProcedure("setMinutes", minutesValue);
        }

        public void setMinutes(JSNumber minutesValue, JSNumber secondsValue) {
            callProcedure("setMinutes", minutesValue, secondsValue);
        }

        public void setMinutes(JSNumber minutesValue, JSNumber secondsValue, JSNumber msValue) {
            callProcedure("setMinutes", minutesValue, secondsValue, msValue);
        }

        public void setMonth(JSNumber monthValue) {
            callProcedure("setMonth", monthValue);
        }

        public void setMonth(JSNumber monthValue, JSNumber dayValue) {
            callProcedure("setMonth", monthValue, dayValue);
        }

        public void setSeconds(JSNumber secondsValue) {
            callProcedure("setSeconds", secondsValue);
        }

        public void setSeconds(JSNumber secondsValue, JSNumber msValue) {
            callProcedure("setSeconds", secondsValue, msValue);
        }

        public void setTime(JSNumber timeValue) {
            callProcedure("setTime", timeValue);
        }

        public void setUTCDate(JSNumber dayValue) {
            callProcedure("setUTCDate", dayValue);
        }

        public void setUTCFullYear(JSNumber yearValue) {
            callProcedure("setUTCFullYear", yearValue);
        }

        public void setUTCFullYear(JSNumber yearValue, JSNumber monthValue) {
            callProcedure("setUTCFullYear", yearValue, monthValue);
        }

        public void setUTCFullYear(JSNumber yearValue, JSNumber monthValue, JSNumber dayValue) {
            callProcedure("setUTCFullYear", yearValue, monthValue, dayValue);
        }

        public void setUTCHours(JSNumber hoursValue) {
            callProcedure("setUTCHours", hoursValue);
        }

        public void setUTCHours(JSNumber hoursValue, JSNumber minutesValue) {
            callProcedure("setUTCHours", hoursValue, minutesValue);
        }

        public void setUTCHours(JSNumber hoursValue, JSNumber minutesValue, JSNumber secondsValue) {
            callProcedure("setUTCHours", hoursValue, minutesValue, secondsValue);
        }

        public void setUTCHours(JSNumber hoursValue, JSNumber minutesValue, JSNumber secondsValue, JSNumber msValue) {
            callProcedure("setUTCHours", hoursValue, minutesValue, secondsValue, msValue);
        }

        public void setUTCMilliseconds(JSNumber millisecondsValue) {
            callProcedure("setUTCMilliseconds", millisecondsValue);
        }

        public void setUTCMinutes(JSNumber minutesValue) {
            callProcedure("setUTCMinutes", minutesValue);
        }

        public void setUTCMinutes(JSNumber minutesValue, JSNumber secondsValue) {
            callProcedure("setUTCMinutes", minutesValue, secondsValue);
        }

        public void setUTCMinutes(JSNumber minutesValue, JSNumber secondsValue, JSNumber msValue) {
            callProcedure("setUTCMinutes", minutesValue, secondsValue, msValue);
        }

        public void setUTCMonth(JSNumber monthValue) {
            callProcedure("setUTCMonth", monthValue);
        }

        public void setUTCMonth(JSNumber monthValue, JSNumber dayValue) {
            callProcedure("setUTCMonth", monthValue, dayValue);
        }

        public void setUTCSeconds(JSNumber secondsValue) {
            callProcedure("setUTCSeconds", secondsValue);
        }

        public void setUTCSeconds(JSNumber secondsValue, JSNumber msValue) {
            callProcedure("setUTCSeconds", secondsValue, msValue);
        }

        public JSString toDateString() {
            return callFunction<JSString>("toDateString");
        }

        public JSString toLocaleDateString() {
            return callFunction<JSString>("toLocaleDateString");
        }

        public JSString toLocaleTimeString() {
            return callFunction<JSString>("toLocaleTimeString");
        }

        public JSString toString() {
            return callFunction<JSString>("toString");
        }

        public JSString toTimeString() {
            return callFunction<JSString>("toTimeString");
        }

        public JSString toUTCString() {
            return callFunction<JSString>("toUTCString");
        }

        public JSString toLocaleString() {
            return callFunction<JSString>("toLocaleString");
        }
    }

    /// <summary>
    /// This class corresponds to javascript Number type.
    /// </summary>
    public class JSNumber: JSVar {

        internal JSNumber(long value)
            : base(value.ToString(), true) {
        }

        internal JSNumber(double value)
            : base(value.ToString(), true) {
        }

        public JSNumber(int value)
            : base(value.ToString(), false) {
        }

        internal JSNumber(int value, bool inline)
            : base(value.ToString(), inline) {
        }

        public JSNumber(JSVar value)
            : base(value) {
        }

        /// <summary>
        /// Implicit constructor. For example:
        ///   JSNumber a = 10;
        ///   
        /// will generate:
        ///   var _1=10
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSNumber(int value) {
            return new JSNumber(value, true);
        }

        /// <summary>
        /// Implicit constructor. For example:
        ///   JSNumber a = 10;
        ///   
        /// will generate:
        ///   var _1=10
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSNumber(long value) {
            return new JSNumber(value);
        }

        /// <summary>
        /// Implicit constructor. For example:
        ///   JSNumber a = 1.0;
        ///   
        /// will generate:
        ///   var _1=1.0
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSNumber(double value) {
            return new JSNumber(value);
        }

        public static implicit operator JSString(JSNumber value) {
            return new JSString(value);
        }

        public static JSNumber operator +(JSNumber number1, JSNumber number2) {
            return new JSNumber(new JSVar(number1 + "+" + number2));
        }

        public static JSNumber operator -(JSNumber number1, JSNumber number2) {
            return new JSNumber(new JSVar(number1 + "-" + number2));
        }

        public static JSNumber operator *(JSNumber number1, JSNumber number2) {
            return new JSNumber(new JSVar(number1 + "*" + number2));
        }

        public static JSNumber operator /(JSNumber number1, JSNumber number2) {
            return new JSNumber(new JSVar(number1 + "/" + number2));
        }

        public static JSNumber operator %(JSNumber number1, JSNumber number2) {
            return new JSNumber(new JSVar(number1 + "%" + number2));
        }


        public static JSExpr operator >(JSNumber number1, JSNumber number2) {
            return new JSExpr(number1 + ">" + number2);
        }

        public static JSExpr operator <(JSNumber number1, JSNumber number2) {
            return new JSExpr(number1 + "<" + number2);
        }

        public static JSNumber operator ++(JSNumber number) {
            //            return new JSNumber(new JSVar(number + "++"));
            JSContext.context.writeln(number.ToString() + "++");
            return number;
        }

        public static JSNumber operator --(JSNumber number) {
            return new JSNumber(new JSVar(number + "--"));
        }

        public static JSNumber operator -(JSNumber number) {
            return new JSNumber(new JSVar("-" + number));
        }

        public static JSNumber MAX_VALUE {
            get {
                return JSVar.getExistingVariable<JSVar>("Number").getProperty<JSNumber>("MAX_VALUE");
            }
        }

        public static JSNumber MIN_VALUE {
            get {
                return JSVar.getExistingVariable<JSVar>("Number").getProperty<JSNumber>("MIN_VALUE");
            }
        }

        public static JSVar NaN {
            get {
                return JSVar.getExistingVariable<JSVar>("Number").getProperty<JSVar>("NaN");
            }
        }

        public static JSVar NEGATIVE_INFINITY {
            get {
                return JSVar.getExistingVariable<JSVar>("Number").getProperty<JSVar>("NEGATIVE_INFINITY");
            }
        }

        public static JSVar POSITIVE_INFINITY {
            get {
                return JSVar.getExistingVariable<JSVar>("Number").getProperty<JSVar>("POSITIVE_INFINITY");
            }
        }

        public static JSNumber parseInt(JSString value) {
            return new JSNumber(new JSVar("parseInt(" + value.ToString() + ")"));
        }

        public static JSNumber parseFloat(JSString value) {
            return new JSNumber(new JSVar("parseFloat(" + value.ToString() + ")"));
        }

        public JSString toExponential() {
            return callFunction<JSString>("toExponential");
        }

        public JSString toExponential(JSNumber fractionDigits) {
            return callFunction<JSString>("toExponential", fractionDigits);
        }

        public JSString toFixed() {
            return callFunction<JSString>("toFixed");
        }

        public JSString toFixed(JSNumber digits) {
            return callFunction<JSString>("toFixed", digits);
        }

        public JSString toLocaleString() {
            return callFunction<JSString>("toLocaleString");
        }

        public JSString toPrecision() {
            return callFunction<JSString>("toPrecision");
        }

        public JSString toPrecision(JSNumber precision) {
            return callFunction<JSString>("toPrecision", precision);
        }

        public JSString toString() {
            return callFunction<JSString>("toString");
        }

        public JSString toString(JSNumber radix) {
            return callFunction<JSString>("toString", radix);
        }
    }

    /// <summary>
    /// This class corresponds to Object in javascript.
    /// </summary>
    /// 
    public class JSObject: JSVar {

        public JSObject()
            : base("{}") {
        }

        public JSObject(JSVar src)
            : base(src) {
        }

        public JSObject(JSVar[] array)
            : base("new Array(" + array.Length + ")") {
            if (array != null && array.Length > 0) {
                for (int i = 0; i < array.Length; i++) {
                    this.setIndex(i, array[i]);
                }
            }
        }

        public static implicit operator JSObject(JSVar[] array) {
            return new JSObject(array);
        }

        public JSVar this[JSString name] {
            get {
                return getIndex<JSVar>(name);
            }
            set {
                setIndex(name, value);
            }
        }
    }

    /// <summary>
    /// JSString represents javascript String type.
    /// </summary>
    /// 
    public class JSString: JSVar {

        /// <summary>
        /// This is an internal constructor.
        /// </summary>
        /// <param name="value"></param>
        internal JSString(string value, bool inline)
            : base(value, inline) {
        }

        public JSString(string value)
            : base("'" + value + "'", false) {
        }

        /// <summary>
        /// This merely copies name.
        /// </summary>
        /// <param name="value"></param>
        public JSString(JSVar value)
            : base(value) {
        }

        /// <summary>
        /// Implicit constructor. For example:
        ///   JSString a = "test";
        ///   
        /// will generate:
        ///   var _1='test'
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static implicit operator JSString(string value) {
            return new JSString("'" + value + "'", true);
        }

        public static JSExpr operator ==(JSString val1, string val2) {
            return new JSExpr(val1.ToString() + "==" + "'" + val2 + "'");
        }

        public static JSExpr operator !=(JSString val1, string val2) {
            return new JSExpr(val1.ToString() + "!=" + "'" + val2 + "'");
        }

        public JSNumber length {
            get {
                return getProperty<JSNumber>("length");
            }
        }

        public JSString charAt(JSNumber index) {
            return callFunction<JSString>("charAt", index);
        }

        public JSString charCodeAt(JSNumber index) {
            return callFunction<JSString>("charCodeAt", index);
        }

        public JSString concat(JSString string2, params JSString[] strings) {
            return callFunction<JSString>("concat", string2, strings);
        }

        public JSNumber indexOf(JSString searchValue) {
            return callFunction<JSNumber>("indexOf", searchValue);
        }

        public JSNumber indexOf(JSString searchValue, JSNumber fromIndex) {
            return callFunction<JSNumber>("indexOf", searchValue, fromIndex);
        }

        public JSNumber lastIndexOf(JSString searchValue) {
            return callFunction<JSNumber>("lastIndexOf", searchValue);
        }

        public JSNumber lastIndexOf(JSString searchValue, JSNumber fromIndex) {
            return callFunction<JSNumber>("lastIndexOf", searchValue, fromIndex);
        }

        public JSString slice(JSNumber beginslice) {
            return callFunction<JSString>("slice", beginslice);
        }

        public JSString slice(JSNumber beginslice, JSNumber endSlice) {
            return callFunction<JSString>("slice", beginslice, endSlice);
        }

        public JSArray<JSString> split(JSString separator) {
            return callFunction<JSArray<JSString>>("split", separator);
        }

        public JSArray<JSString> split(JSString separator, JSNumber limit) {
            return callFunction<JSArray<JSString>>("split", separator, limit);
        }

        public JSString substring(JSNumber indexA) {
            return callFunction<JSString>("substring", indexA);
        }

        public JSString substring(JSNumber indexA, JSNumber indexB) {
            return callFunction<JSString>("substring", indexA, indexB);
        }

        public JSString toLowerCase() {
            return callFunction<JSString>("toLowerCase");
        }

        public JSString toUpperCase() {
            return callFunction<JSString>("toUpperCase");
        }
    }

    public class JSArray<T>: JSVar where T: JSVar {

        public JSArray(JSVar src)
            : base(src) {
        }

        public JSArray(JSNumber arrayLength)
            : base(callConstructor("Array", arrayLength)) {
        }

        public JSArray(T[] array)
            : this(array.Length) {
            if (array != null && array.Length > 0) {
                for (int i = 0; i < array.Length; i++) {
                    this.setIndex(i, array[i]);
                }
            }
        }

        public static implicit operator JSArray<T>(T[] array) {
            return new JSArray<T>(array);
        }

        public JSNumber length {
            get {
                return getProperty<JSNumber>("length");
            }
        }

        public T this[JSNumber index] {
            get {
                return getIndex<T>(index.ToString());
            }
            set {
                setProperty(name, value);
            }
        }

        public T pop() {
            return callFunction<T>("pop");
        }

        public void push(T[] elements) {
            callProcedure("push", elements);
        }

        public void reverse() {
            callProcedure("reverse");
        }

        public T shift() {
            return callFunction<T>("shift");
        }

        public void sort() {
            callProcedure("sort");
        }

        public JSArray<T> splice(JSNumber index) {
            return callFunction<JSArray<T>>("splice");
        }

        public JSArray<T> splice(JSNumber index, JSNumber howMany) {
            return callFunction<JSArray<T>>("splice", index, howMany);
        }

        public JSArray<T> splice(JSNumber index, JSNumber howMany, T[] elements) {
            return callFunction<JSArray<T>>("splice", index, howMany, elements);
        }

        public JSNumber unshift(T[] elements) {
            return callFunction<JSNumber>("unshift", elements);
        }

        public JSArray<T> concat(T value1, params T[] values) {
            return callFunction<JSArray<T>>("concat", value1, values);
        }

        public JSString join(JSString separator) {
            return callFunction<JSString>("join", separator);
        }

        public JSArray<T> slice(JSNumber begin) {
            return callFunction<JSArray<T>>("slice", begin);
        }

        public JSArray<T> slice(JSNumber begin, JSNumber end) {
            return callFunction<JSArray<T>>("slice", begin, end);
        }

        public JSString toString() {
            return callFunction<JSString>("toString");
        }
    }

    /// <summary>
    /// This delegate is used to enclose block statements.
    /// </summary>
    public delegate void function();

    /// <summary>
    /// This class coresponds to javascript Function.
    /// </summary>
    public class JSFunction: JSVar {

        public JSFunction(Delegate d, params object[] args) {

            StringBuilder sb = new StringBuilder("function(");
            if (args != null && args.Length > 0) {
                for (int i = 0; i < args.Length; i++) {
                    sb.Append(args[i]);
                    if (i < args.Length - 1) {
                        sb.Append(",");
                    }
                }
            }
            sb.Append("){");


            JSVar func = new JSVar(sb.ToString());
            d.DynamicInvoke(args);
            JSContext.context.writeln("}");

            this.name = func.ToString();
        }

        /// <summary>
        /// Global function with a name.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="d"></param>
        /// <param name="args"></param>
        public JSFunction(string name, Delegate d, params object[] args) {
            this.name = name;

            StringBuilder sb = new StringBuilder("function(");
            if (args != null && args.Length > 0) {
                for (int i = 0; i < args.Length; i++) {
                    sb.Append(args[i]);
                    if (i < args.Length - 1) {
                        sb.Append(",");
                    }
                }
            }
            sb.Append("){");


            JSContext context = JSContext.context;
            context.writeln("window." + name + "=" + sb.ToString());
            d.DynamicInvoke(args);
            context.writeln("}");
        }

        public static implicit operator JSFunction(function func) {
            return new JSFunction(func);
        }

        public void call(params object[] args) {
            callProcedure(this.ToString(), args);
        }
    }

    /// <summary>
    /// JSExpr is for any javascript expression that is represented as string.
    /// It merely provides a type that distinguishes from string.
    /// </summary>
    /// 
    public class JSExpr: JSString {
        public JSExpr(JSVar src)
            : base(src) {
        }

        public JSExpr(string expr)
            : base(expr, true) {
        }

        public static implicit operator JSExpr(JSBoolean src) {
            return new JSExpr(src.ToString());
        }

        public static implicit operator JSExpr(JSNumber src) {
            return new JSExpr(src.ToString());
        }

        public static implicit operator JSExpr(string src) {
            return new JSExpr(src);
        }
    }

    public delegate void catch_statement(JSException e);

    public class JSException: JSVar {

        public JSException(JSVar src)
            : base(src) {
        }

        public JSException() {
            name = "e";
        }
    }

    /// <summary>
    /// This class is for any statement in form of string.
    /// </summary>
    public class JSStmt {

        protected JSStmt() {
        }

        public static void FOR(JSExpr condition, JSExpr final_expression, function statement) {
            JSContext context = JSContext.context;
            context.writeln("for(;" + condition + ";" + final_expression + "){");
            statement.DynamicInvoke();
            context.writeln("}");
        }

        public static void FOR(JSExpr condition, function statement) {
            JSContext context = JSContext.context;
            context.writeln("for(;" + condition + ";){");
            statement.DynamicInvoke();
            context.writeln("}");
        }

        public static void FOR_IN(JSVar iter, JSVar obj, function statement) {
            JSContext context = JSContext.context;
            context.writeln("for(" + iter.ToString() + " in " + obj.ToString() + "){");
            statement.DynamicInvoke();
            context.writeln("}");
        }

        public static void FOR_EACH_IN(JSVar iter, JSVar obj, function statement) {
            JSContext context = JSContext.context;
            context.writeln("for each(" + iter.ToString() + " in " + obj.ToString() + "){");
            statement.DynamicInvoke();
            context.writeln("}");
        }

        /// <summary>
        /// This represents javascript "if" block statement.
        /// Example:
        ///   IF(..., () => {
        ///       ....
        ///   });
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="ifBlock"></param>
        public static void IF(JSExpr condition, function ifBlock) {
            JSContext context = JSContext.context;
            context.writeln("if(" + condition + "){");
            ifBlock.DynamicInvoke();
            context.writeln("}");
        }

        /// <summary>
        /// This represents javascript "if" block statement.
        /// Example:
        ///   IF(..., () => {
        ///       ....
        ///   }, () => {
        ///       ....
        ///   });
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="ifBlock"></param>
        /// 
        public static void IF_ELSE(JSExpr condition, function ifBlock, function elseBlock) {
            JSContext context = JSContext.context;
            context.writeln("if(" + condition.ToString() + "){");
            ifBlock.DynamicInvoke(null);
            context.writeln("}else{");
            elseBlock.DynamicInvoke(null);
            context.writeln("}");
        }

        public static T TERNARY<T>(JSExpr condition, JSVar t, JSVar f) {
            JSVar v = new JSVar(new JSVar(condition.ToString() + "?" + t + ":" + f));
            return JSVar.createVariable<T>(v);
        }

        public static void RETURN() {
            JSContext.context.writeln("return");
        }

        public static void BREAK() {
            JSContext.context.writeln("break");
        }

        public static JSExpr AND(JSExpr stmt1, JSExpr stmt2) {
            return new JSExpr(stmt1 + " && " + stmt2);
        }

        public static JSExpr AND(JSVar stmt1, JSExpr stmt2) {
            return new JSExpr(stmt1 + " && " + stmt2);
        }

        public static void WHILE(JSExpr condition, function statement) {
            JSContext context = JSContext.context;
            context.writeln("while(" + condition + "){");
            statement.DynamicInvoke();
            context.writeln("}");
        }

        public static void DO_WHILE(function statement, JSExpr condition) {
            JSContext context = JSContext.context;
            context.writeln("do{");
            statement.DynamicInvoke();
            context.writeln("}");
            context.writeln("while(" + condition + ")");
        }

        public static void TRY_CATCH(function try_statements, catch_statement catch_statements) {
            JSException e = new JSException();
            JSContext context = JSContext.context;
            context.writeln("try{");
            try_statements.DynamicInvoke();
            context.writeln("}catch(" + e.ToString() + "){");
            catch_statements.DynamicInvoke();
            context.writeln("}");
        }

        public static void TRY_CATCH_FINALLY(function try_statements,
                                             catch_statement catch_statements,
                                             function finally_statements) {
            JSException e = new JSException();
            JSContext context = JSContext.context;
            context.writeln("try{");
            try_statements.DynamicInvoke();
            context.writeln("}catch(" + e.ToString() + "){");
            catch_statements.DynamicInvoke(e);
            context.writeln("}finally{");
            finally_statements.DynamicInvoke();
            context.writeln("}");
        }
    }

    public class JSMath: JSVar {

        protected static JSMath instance = new JSMath();

        protected JSMath() {
            name = "Math";
        }

        public static JSNumber abs(JSNumber x) {
            return instance.callFunction<JSNumber>("abs", x);
        }

        public static JSNumber acos(JSNumber x) {
            return instance.callFunction<JSNumber>("acos", x);
        }

        public static JSNumber asin(JSNumber x) {
            return instance.callFunction<JSNumber>("asin", x);
        }

        public static JSNumber atan(JSNumber x) {
            return instance.callFunction<JSNumber>("atan", x);
        }

        public static JSNumber atan2(JSNumber y, JSNumber x) {
            return instance.callFunction<JSNumber>("atan", y, x);
        }

        public static JSNumber ceil(JSNumber x) {
            return instance.callFunction<JSNumber>("ceil", x);
        }

        public static JSNumber cos(JSNumber x) {
            return instance.callFunction<JSNumber>("cos", x);
        }

        public static JSNumber exp(JSNumber x) {
            return instance.callFunction<JSNumber>("exp", x);
        }

        public static JSNumber floor(JSNumber x) {
            return instance.callFunction<JSNumber>("floor", x);
        }

        public static JSNumber log(JSNumber x) {
            return instance.callFunction<JSNumber>("log", x);
        }

        public static JSNumber max(JSNumber x) {
            return instance.callFunction<JSNumber>("max", x);
        }

        public static JSNumber min(JSNumber x) {
            return instance.callFunction<JSNumber>("min", x);
        }

        public static JSNumber pow(JSNumber theBase, JSNumber exponent) {
            return instance.callFunction<JSNumber>("min", theBase, exponent);
        }

        public static JSNumber random() {
            return instance.callFunction<JSNumber>("random");
        }

        public static JSNumber round(JSNumber x) {
            return instance.callFunction<JSNumber>("round", x);
        }

        public static JSNumber sin(JSNumber x) {
            return instance.callFunction<JSNumber>("sin", x);
        }

        public static JSNumber sqrt(JSNumber x) {
            return instance.callFunction<JSNumber>("sqrt", x);
        }

        public static JSNumber tan(JSNumber x) {
            return instance.callFunction<JSNumber>("tan", x);
        }
    }

    public class JSRegExp: JSVar {

        public JSRegExp(JSVar src)
            : base(src) {
        }

        public JSRegExp(JSString pattern)
            : base(callConstructor("RegExp", pattern)) {
        }

        public JSRegExp(JSString pattern, JSString flags)
            : base(callConstructor("RegExp", pattern, flags)) {
        }

        public JSBoolean global {
            get {
                return getProperty<JSBoolean>("global");
            }
        }

        public JSBoolean ignoreCase {
            get {
                return getProperty<JSBoolean>("ignoreCase");
            }
        }

        public JSNumber lastIndex {
            get {
                return getProperty<JSNumber>("lastIndex");
            }

            set {
                setProperty("lastIndex", value);
            }
        }

        public JSBoolean multiline {
            get {
                return getProperty<JSBoolean>("multiline");
            }
        }

        public JSString source {
            get {
                return getProperty<JSString>("source");
            }
        }

        public JSArray<JSVar> exec(JSString str) {
            return callFunction<JSArray<JSVar>>("exec", str);
        }

        public JSBoolean test() {
            return callFunction<JSBoolean>("test");
        }

        public JSBoolean test(JSString str) {
            return callFunction<JSBoolean>("test", str);
        }
    }
}
